package utilities;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import enumeration.ArgumentToken;
import enumeration.Operation;
import exception.InvalidInputException;

import entities.ParamsObject;

/**
 * 
 * @author ahosick
 *
 */
public class ArgumentParser {

	public static final String TOKEN_DELIMITER = "`~`";
	/** replace && with appropriate token */
	private static final String FIND_ARGS_AFTER_TOKEN = "(?<=`~`&&`~` (?:`~`[^-][\\w\\\\ :_\\./-]{1,99}`~` )?)(?:`~`([^-][\\w\\\\ :_\\./-]+?)`~`)";
	/** replace && with appropriate token */
	private static final String FIND_TOKEN = "\\s?`~`&&`~`\\s";
	private static final String FIND_INVALID_TOKENS = "\\s?`~`(-[^ftxauo]{1})|(-\\w{2,})`~`\\s";
	private static final String FIND_OPERATION_KEYWORD = "^`~`([A-Za-z]+?)`~`";
	private static final String FIND_HELP_PARAMETER = "^`~`help`~`\\s`~`(.*?)`~`";

	/**
	 * 
	 * @param args
	 * @return
	 */
	public static ParamsObject parseArguments(String argString, boolean fromFile) throws InvalidInputException{
		ParamsObject po = new ParamsObject();
		String keyWord;
		//get the operation key word
		Pattern pattern = Pattern.compile(FIND_OPERATION_KEYWORD);
		Matcher match = pattern.matcher(argString);
		if( match.find() ) {
			keyWord = match.group(1);
		} else {
			throw new InvalidInputException("An operation must be specified change, " +
					"swap, attribute, analyze, or help");
		}
		//try to match the keyword to an operation
		//HELP
		if( "help".equalsIgnoreCase(keyWord) ) {
			//direct to the correct parsing method	
			po = parseForHelp(argString);
		}
		//CHANGE NODE
		else if ("change".equalsIgnoreCase(keyWord)){
			po = parseForChange(argString);
		}
		//SWAP NODE
		else if ("swap".equalsIgnoreCase(keyWord)) {
			po = parseForSwap(argString);
		}
		//CHANGE ATTRIBUTE
		else if ("attribute".equalsIgnoreCase(keyWord)) {
			po = parseForAttribute(argString);
		}
		//ANALYZE
		else if ("analyze".equalsIgnoreCase(keyWord)) {
			po = parseForAnalyze(argString);
		}
		//Invalid Operation
		else {
			throw new InvalidInputException("[" + keyWord + "] is not a valid operation");
		}

		po.setFromFile(fromFile);
		return po;
	}
	
	public static ParamsObject parseForHelp(String argString){
		ParamsObject po = new ParamsObject();
		po.setOperation(Operation.HELP);
		String param = null;
		Pattern pattern = Pattern.compile(FIND_HELP_PARAMETER);
		Matcher match = pattern.matcher(argString);
		if(match.find()){
			param = match.group(1);
			if(param.equalsIgnoreCase("swap")){
				po.setOperation(Operation.HELP_SWAP_JMETER_NODE);
			}
			else if (param.equalsIgnoreCase("analyze")){
				po.setOperation(Operation.HELP_ANALYZE);
			}
			else if(param.equalsIgnoreCase("change")){
				po.setOperation(Operation.HELP_CHANGE_JMETER_NODE);
			}
			else if (param.equalsIgnoreCase("attribute")){
				po.setOperation(Operation.HELP_CHANGE_JMETER_ATTRIBUTE);
			}
		}
		return po;
	}
	
	public static ParamsObject parseForAnalyze(String argString) throws InvalidInputException {
		ParamsObject po = new ParamsObject();
		//check for invalid tokens
		checkForInvalidTokens(argString);
		//check for tokens not used in analyze
		checkForTokensNotUsedInOperation(Operation.ANALYZE, argString);
		//Check for file token '-f'
		if(checkStringContainsToken(ArgumentToken.FILE, argString) ) {
			po.setFileName( findJMXFileAfterToken(ArgumentToken.FILE, argString) );
		} else {
			throw new InvalidInputException("You must have a target file[-f]");
		}
		//IF contains output token '-o'
		if( checkStringContainsToken(ArgumentToken.OUTPUT, argString) ){
			po.setOutputFile(findParamAfterToken(ArgumentToken.OUTPUT, argString));
		}
		po.setOperation(Operation.ANALYZE);
		return po;
	}
	
	public static ParamsObject parseForChange(String argString) throws InvalidInputException{
		ParamsObject po = new ParamsObject();
		//check for invalid tokens
		checkForInvalidTokens(argString);
		//check for tokens not used in analyze
		checkForTokensNotUsedInOperation(Operation.CHANGE_JMETER_NODE, argString);
		//Check for file token '-f'
		if(checkStringContainsToken(ArgumentToken.FILE, argString) ) {
			po.setFileName( findJMXFileAfterToken(ArgumentToken.FILE, argString) );
		} else {
			throw new InvalidInputException("You must have a target jmx file[-f]");
		}
		//Check for file token '-t'
		if(checkStringContainsToken(ArgumentToken.TEST_NAME, argString) ) {
			po.setTestName( findParamAfterToken(ArgumentToken.TEST_NAME, argString) );
		} else {
			throw new InvalidInputException("You must specify a test name to change[-t]");
		}
		//Check for file token '-x'
		if(checkStringContainsToken(ArgumentToken.XML, argString) ) {
			po.setXmlPiece( findXMLFileAfterToken(ArgumentToken.XML, argString) );
		} else {
			throw new InvalidInputException("You must have a target xml file[-x]");
		}
		//IF contains output token '-o' optional
		if( checkStringContainsToken(ArgumentToken.OUTPUT, argString) ){
			po.setOutputFile(findParamAfterToken(ArgumentToken.OUTPUT, argString));
		}
		po.setOperation(Operation.CHANGE_JMETER_NODE);
		return po;
	}
	
	public static ParamsObject parseForSwap(String argString) throws InvalidInputException {
		ParamsObject po = new ParamsObject();
		//check for invalid tokens
		checkForInvalidTokens(argString);
		//check for tokens not used in analyze
		checkForTokensNotUsedInOperation(Operation.SWAP_JMETER_NODE, argString);
		//Check for file token '-f'
		if(checkStringContainsToken(ArgumentToken.FILE, argString) ) {
			po.setFileName( findJMXFileAfterToken(ArgumentToken.FILE, argString) );
		} else {
			throw new InvalidInputException("You must have a target jmx file[-f]");
		}
		//Check for test name token '-t'
		if(checkStringContainsToken(ArgumentToken.TEST_NAME, argString) ) {
			po.setTestName( findParamAfterToken(ArgumentToken.TEST_NAME, argString) );
		} else {
			throw new InvalidInputException("You must specify a test name to change[-t]");
		}
		//Check for xml file token '-x'
		if(checkStringContainsToken(ArgumentToken.XML, argString) ) {
			po.setXmlPiece( findXMLFileAfterToken(ArgumentToken.XML, argString) );
		} else {
			throw new InvalidInputException("You must have a target xml file[-x]");
		}
		//Check for xml test '-xt'
		if( checkStringContainsToken(ArgumentToken.XML_TEST, argString) ) {
			po.setXmlTestName( findParamAfterToken(ArgumentToken.XML_TEST, argString) );
		} else {
			throw new InvalidInputException("Must specify a source test name[-xt]");
		}
		//IF contains output token '-o' optional
		if( checkStringContainsToken(ArgumentToken.OUTPUT, argString) ){
			po.setOutputFile(findParamAfterToken(ArgumentToken.OUTPUT, argString));
		}
		po.setOperation(Operation.SWAP_JMETER_NODE);
		return po;
	}
	
	public static ParamsObject parseForAttribute(String argString) throws InvalidInputException {
		ParamsObject po = new ParamsObject();
		//check for invalid tokens
		checkForInvalidTokens(argString);
		//check for tokens not used in analyze
		checkForTokensNotUsedInOperation(Operation.CHANGE_JMETER_ATTRIBUTE, argString);
		//Check for file token '-f'
		if(checkStringContainsToken(ArgumentToken.FILE, argString) ) {
			po.setFileName( findJMXFileAfterToken(ArgumentToken.FILE, argString) );
		} else {
			throw new InvalidInputException("You must have a target jmx file[-f]");
		}
		//Check for test name token '-t'
		if(checkStringContainsToken(ArgumentToken.TEST_NAME, argString) ) {
			po.setTestName( findParamAfterToken(ArgumentToken.TEST_NAME, argString) );
		} else {
			throw new InvalidInputException("You must specify a test name to change[-t]");
		}
		//check for attribute '-a'
		if(checkStringContainsToken(ArgumentToken.ATTRIBUTE, argString) ) {
			po.setAttribute( findParamAfterToken(ArgumentToken.ATTRIBUTE, argString) );
		} else {
			throw new InvalidInputException("You must specify an attribute to change[-a]");
		}
		//check for user input '-u'
		if(checkStringContainsToken(ArgumentToken.USER_INPUT, argString) ) {
			po.setUserInput( findParamAfterToken(ArgumentToken.USER_INPUT, argString) );
		} else {
			throw new InvalidInputException("You must give input to change an attribute [-u]");
		}
		//IF contains output token '-o' optional
		if( checkStringContainsToken(ArgumentToken.OUTPUT, argString) ){
			po.setOutputFile(findParamAfterToken(ArgumentToken.OUTPUT, argString));
		}
		po.setOperation(Operation.CHANGE_JMETER_ATTRIBUTE);
		return po;
	}
	
	public static String stringMaker(String[] args) {

		StringBuilder builder = new StringBuilder();
		for (String token : args) {
			builder.append(TOKEN_DELIMITER);
			builder.append(token);
			builder.append(TOKEN_DELIMITER + " ");
		}
		String argString = builder.toString();
		return argString;
	}
		
	/**
	 * Searches the arg string for the passed token.
	 * @param token
	 * @param argString
	 * @return true if the token is found; else returns false
	 * @throws InvalidInputException if passed token is passed more than once
	 */
	public static boolean checkStringContainsToken(ArgumentToken token, String argString) throws InvalidInputException{
		boolean found = false;
		int count = 0;
		//Replace && with the token
		String regexWithToken = FIND_TOKEN.replace("&&", token.KeyWord());
		Pattern pattern = Pattern.compile(regexWithToken);
		Matcher match = pattern.matcher(argString);
		while(match.find()){
			//if more than result is found throw an exception
			if(count > 0){
				throw new InvalidInputException(token.KeyWord() + "Can only be used once");
			}
			//result has been found; set to true
			found = true;
		}
		return found;
	}
		
	/**
	 * checks the argString for invalid tokens (not -f -t -x -xt -a -u or -o)
	 * throws exception of an invalid token is found
	 * @param argString
	 * @throws InvalidInputException 
	 */
	public static void checkForInvalidTokens(String argString) throws InvalidInputException{
		Pattern pattern = Pattern.compile(FIND_INVALID_TOKENS);
		Matcher match = pattern.matcher(argString);
		String group1;
		String group2;
		while(match.find()){
			group1 = match.group(1);
			group2 = match.group(2);
			if(group1 != null){
				throw new InvalidInputException(match.group(1) + " is not a valid argument");
			}
			else if(group2 !=null && !group2.equals("-xt")){
				throw new InvalidInputException(match.group(2) + " is not a valid argument");
			}
		}
	}
		
		/**
		 * Searches the arg string for parameters following the passed token
		 * and returns the found string
		 * @param token
		 * @param argString
		 * @return
		 * @throws InvalidInputException: when more than on parameter is found
		 * following the token or if no parameters are found
		 */
		public static String findParamAfterToken(ArgumentToken token, String argString) throws InvalidInputException{
			String param = null;
			int count = 0;
			//Replace && with the token
			String regexWithToken = FIND_ARGS_AFTER_TOKEN.replace("&&", token.KeyWord());
			Pattern pattern = Pattern.compile(regexWithToken);
			Matcher match = pattern.matcher(argString);
			while(match.find()){
				//if more than result is found throw an exception
				if(count > 0){
					throw new InvalidInputException("Only one parameter is allowed for argument " + token.KeyWord());
				}
				//set parameter to the result
				param = match.group(1);
				//increment count
				count++;
			}
			//if no match is found throw an exception
			if(param == null){
				throw new InvalidInputException("No parameter found for argument " + token.KeyWord());
			}
			
			//trim quotes from the the beginning and end
			//param = param.substring(1, param.length() - 1);
			
			return param;
		}
		
		/**
		 * Searches the arg string for parameters following the passed token
		 * that end in .jmx and returns the found string
		 * @param token
		 * @param argString
		 * @return
		 * @throws InvalidInputException
		 */
		public static String findJMXFileAfterToken(ArgumentToken token, String argString) throws InvalidInputException{
			String param = findParamAfterToken(token, argString);
			if(!param.endsWith(".jmx")){
				throw new InvalidInputException(token.KeyWord() + " takes a JMX file");
			}
			return param;
		}
		
		/**
		 * Searches the arg string for parameters following the passed token
		 * that end in .jmx or .xml and returns the found string
		 * @param token
		 * @param argString
		 * @return
		 * @throws InvalidInputException
		 */
		public static String findXMLFileAfterToken(ArgumentToken token, String argString) throws InvalidInputException{
			String param = findParamAfterToken(token, argString);
			if(param.endsWith(".jmx") || param.endsWith(".xml")){
				return param;
			} else {
				throw new InvalidInputException(token.KeyWord() + " takes an xml OR jmx file");
			}
		}
		
	
	/**
	 * @throws InvalidInputException 
	 * 
	 */
	public static void checkForTokensNotUsedInOperation(Operation operation, String argString) throws InvalidInputException{
		String regexWithToken;
		Pattern pattern;
		Matcher match;
		switch (operation){
		case ANALYZE: //-t -x -xt -a -u
			regexWithToken = FIND_TOKEN.replace("&&", ArgumentToken.TEST_NAME.KeyWord() + "|" +
					ArgumentToken.XML_TEST  + "|" + ArgumentToken.XML.KeyWord() + "|"
					+ ArgumentToken.ATTRIBUTE.KeyWord() + "|" + ArgumentToken.USER_INPUT.KeyWord());
			pattern = Pattern.compile(regexWithToken);
			match = pattern.matcher(argString);
			if (match.find()) {
				throw new InvalidInputException("Argument [" + match.group() + "] Does not apply to the operation Analyze");
			}
			break;
		case SWAP_JMETER_NODE: // -xt -a -u
			regexWithToken = FIND_TOKEN.replace("&&", ArgumentToken.ATTRIBUTE.KeyWord() + "|" + ArgumentToken.USER_INPUT.KeyWord());
			pattern = Pattern.compile(regexWithToken);
			match = pattern.matcher(argString);
			if (match.find()) {
				throw new InvalidInputException("Argument [" + match.group() + "] Does not apply to the operation Swap Node");
			}
			break;
		case CHANGE_JMETER_NODE: //-a -u
			regexWithToken = FIND_TOKEN.replace("&&", ArgumentToken.XML_TEST  + "|" +
					ArgumentToken.ATTRIBUTE.KeyWord() + "|" + ArgumentToken.USER_INPUT.KeyWord());
			pattern = Pattern.compile(regexWithToken);
			match = pattern.matcher(argString);
			if (match.find()) {
				throw new InvalidInputException("Argument [" + match.group() + "] Does not apply to the operation Change Node");
			}
			break;
		case CHANGE_JMETER_ATTRIBUTE : //-x -xt
			regexWithToken = FIND_TOKEN.replace("&&", ArgumentToken.XML  + "|" +
					ArgumentToken.XML_TEST.KeyWord());
			pattern = Pattern.compile(regexWithToken);
			match = pattern.matcher(argString);
			if (match.find()) {
				throw new InvalidInputException("Argument [" + match.group() + "] Does not apply to the operation Change Attribute");
			}
			break;
		}
	}
}
     